﻿using UnityEngine;
using System.Collections;
using System.Linq;
using FloodFill;
using System.Collections.Generic;
using Engine.Entities.Character;
using Assets.Engine.AI.GeneticAlgorithm;
using System;

public class FrenzyPeopleController : MonoBehaviour {

    public static FrenzyPeopleController singleton;

    public static FrenzyPeopleController instance
    {
        get
        {
            return FrenzyPeopleController.singleton;
        }
    }

    public GameObject MalePrefab;
    public GameObject FemalePrefab;
    public TextAsset NamesITA;
    public TextAsset SurnameITA;

    //massimo numero consentito di persone nel party
    public int SizeParty = 6;
    public int StartHP = 20;
    public int StartHappiness = 60;

    //variabile usata per l'accoppiamento
    public GeneticVariables geneticVariable = new GeneticVariables();

    public int characterCount {
        get { return contCharacter; }
    }
    private int contCharacter = 0;

    public int partyCount {
        get { return contParty; } 
    }
    private int contParty = 0;

    private FrenzyIslandContentGenerator _frenzyContentGenerator;
    private FrenzyIslandGenerator frenzyIslandGenerator;
    private GameObject _peopleContainer;

    private SteveNames steveNames { get; set; }

    private SteveNames steveSurname { get; set; }

    /// <summary>
    /// metadata per le skill dei personaggi 
    /// Quando è settato a true, è necessario ricalcolare tutte le medie dei personaggi
    /// </summary>
    internal bool Metadata_IsDirty = true;

    private Dictionary<SkillsEnum, int> sumSkillDict = new Dictionary<SkillsEnum, int>();
    private Dictionary<SkillsEnum, int> maxSkillDict = new Dictionary<SkillsEnum, int>();
    private Dictionary<SkillsEnum, int> minSkillDict = new Dictionary<SkillsEnum, int>();

    private const int START_MAX_VALUE = 0;
    private const int START_MIN_VALUE = 10000; 

    public List<SteveBrain> SteveList { get; set; }

    public List<PartyController> PartyList { get; set; }

	void Awake()
    {
        //creo la classe singoletto
        if (FrenzyPeopleController.singleton != null)
        {
            DestroyImmediate(this);
        }
        else
        {
            FrenzyPeopleController.singleton = this;
        }

        //inizializzo le liste
        SteveList = new List<SteveBrain>();
        PartyList = new List<PartyController>();
        contCharacter = 0;
        contParty = 0;

        frenzyIslandGenerator = GetComponent<FrenzyIslandGenerator>();
        _frenzyContentGenerator = GetComponent<FrenzyIslandContentGenerator>();
        _peopleContainer = new GameObject("PeopleContainer");

        InitMetadata();
        LoadNames();

    }

    private void LoadNames()
    {
        steveNames = CSVReader.GetNames(NamesITA);
        steveSurname = CSVReader.GetSurnames(SurnameITA);
    }

    internal void InitMetadata()
    {
        InitDict(ref sumSkillDict, 0);
        InitDict(ref maxSkillDict, START_MAX_VALUE);
        InitDict(ref minSkillDict, START_MIN_VALUE);
    }

    private void ResetDict()
    {
        ResetDict(ref sumSkillDict, 0);
        ResetDict(ref maxSkillDict, START_MAX_VALUE);
        ResetDict(ref minSkillDict, START_MIN_VALUE);
    }

    private void InitDict(ref Dictionary<SkillsEnum, int> dict, int value){
        dict = new Dictionary<SkillsEnum, int>();
        dict.Add(SkillsEnum.Intelligence, value);
        dict.Add(SkillsEnum.Resistance, value);
        dict.Add(SkillsEnum.Strenght, value);
        dict.Add(SkillsEnum.Agility, value);
        dict.Add(SkillsEnum.Charm, value);
        dict.Add(SkillsEnum.Appeal, value);
    }

    private void ResetDict(ref Dictionary<SkillsEnum, int> dict, int value){
        dict[SkillsEnum.Intelligence] = value;
        dict[SkillsEnum.Resistance] = value;
        dict[SkillsEnum.Strenght] = value;
        dict[SkillsEnum.Agility] = value;
        dict[SkillsEnum.Charm] = value;
        dict[SkillsEnum.Appeal] = value;
    }

	void Start () {
	
	}
	
	void Update () {

	}
    
    // Genera una popolazione casuale di individui e l'assegna casualmente in party
    public void GeneratePeople()
    {
        List<SteveBrain> tmpList = new List<SteveBrain>();

        //int inParty = UnityEngine.Random.Range(1, 6);
        int inParty = 6;
        int cont = 0;
        
        for (int i = 0; i < 20; i++){
            tmpList.Add( CreateCharacter() );
            cont++;

            if( cont == inParty)
            {
                if( UnityEngine.Random.Range(0f, 1f) < 0.2f)  AddInParty(tmpList);

                cont = 0;
                inParty = UnityEngine.Random.Range(1, 6);
                tmpList = new List<SteveBrain>();
            }
        }

        if (tmpList.Count != 0)  AddInParty(tmpList);
        
    }

    /// <summary>
    /// istanzia un nuovo personaggio dal prefab e imposta i componenti
    /// </summary>
    public SteveBrain CreateCharacter(SteveModel model = null)
    {
        //prendo una posizione casuale sulla spiaggia
        FrenzyIslandGenerator.VoxelInfo voxelInfo = 
            frenzyIslandGenerator.ListSandy[UnityEngine.Random.Range(0, frenzyIslandGenerator.ListSandy.Count)];

        //istanzio il nuovo personaggio
        GameObject newChar;
        contCharacter++;
        if (contCharacter % 2 == 0) {
            newChar = Instantiate(MalePrefab);
            newChar.GetComponent<SteveBrain>().model.Gender = GenderEnum.Male;
        } else {
            newChar = Instantiate(FemalePrefab);
            newChar.GetComponent<SteveBrain>().model.Gender = GenderEnum.Female;
        }
        newChar.name = "steve_" + contCharacter;
        newChar.transform.parent = _peopleContainer.transform;
        newChar.transform.position = voxelInfo.position + Vector3.up + Vector3.down * 0.5f;

        //aggiungo la nuova istanza alla lista
        SteveList.Add(newChar.GetComponent<SteveBrain>());

        //se ho passato un model, assegno questo al nuovo personaggio
        if (model != null)
        {
            newChar.GetComponent<SteveBrain>().model = model;
        }else{
            //usa quello standard
        }
        
        //assegno un nome e cognome
        SteveModel steveModel = newChar.GetComponent<SteveBrain>().model;
        if (steveModel.Name == SteveNames.NAME_UNDEFINED)
        {
            steveModel.SetName(
                steveNames.GetRandomName(steveModel.Gender),
                steveSurname.GetRandomSurname());
        }

        //imposto gli HP iniziali
        steveModel.HP = StartHP;

        //iposto la felicità iniziale
        steveModel.Happiness = StartHappiness;
        
        return newChar.GetComponent<SteveBrain>();
    }


    #region Party

    public PartyController CreateParty() {
        PartyController party = null;
        //creo un nuovo party
        contParty++;

        //crea un nuovo contenitore del party
        GameObject newParty = new GameObject("party_" + contParty);
        newParty.transform.parent = _peopleContainer.transform;

        //aggiungo un nuovo partyController e lo inserisco nella lista dei party
        party = newParty.AddComponent<PartyController>();
        PartyList.Add(party);
        return party;
    }

    // Aggiunge una lista di individui nel party     
    public void AddInParty(List<SteveBrain> listCharacter, PartyController existsParty = null)
    {
        foreach (SteveBrain item in listCharacter)
            existsParty = AddInParty(item, existsParty);
    }

    // Aggiunge uno steveBrain al Party
    // Se non esiste lo crea e lo restituisce, altrimenti restituisce quello vecchio
    public PartyController AddInParty(SteveBrain brain, PartyController existsParty = null)
    {
        if(existsParty==null)
        {
            //creo un nuovo party
            contParty++;

            //crea un nuovo contenitore del party
            GameObject newParty = new GameObject("party_" + contParty);
            newParty.transform.parent = _peopleContainer.transform;

            //aggiungo un nuovo partyController e lo inserisco nella lista dei party
            existsParty = newParty.AddComponent<PartyController>();
            PartyList.Add(existsParty);
        }

        //cambio il parent dell'elemento
        brain.gameObject.transform.parent = existsParty.transform;

        //assegno il nuovo elemento al party controller e viceversa
        existsParty.PeopleInParty.Add(brain);
        brain.party = existsParty;

        return existsParty;
    }

    // Elimina un party
    public void DestroyParty ( PartyController party ){
        //porta i gameobject fuori dal party
        //rimuove il partycontroller dal suo interno
        foreach (SteveBrain brain in party.PeopleInParty){
            brain.gameObject.transform.parent = _peopleContainer.transform;
            brain.party = null;
        }
        
        //rimuove e cancella il party dalla lista dei party
        PartyList.Remove ( party );

        //distrugge il contenitore del party
        if (party.gameObject != null)
            Destroy(party.gameObject);
    }

    //Rimuovo un elemento dal party
    internal void RemoveFromParty(SteveBrain steveBrain){
        Debug.Log("CALL: RemoveFromParty");

        PartyController party = steveBrain.party;

        //cambio il parent del personaggio e disctruggo il suo party controller
        steveBrain.gameObject.transform.parent = _peopleContainer.transform;
        Destroy( steveBrain.gameObject.GetComponent<PartyController>() );

        //rimuovo il partyController dal gameobject
        party.PeopleInParty.Remove(steveBrain);
        
        steveBrain.party = null;
    }

    #endregion Party

    #region Dictionary Skill Operation

    // Questa funzione ricalcola tutte le medie di tutti i personaggi
    // Setta quindi Metadata_IsDirty a false
    internal void RefreshMetadata()
    {
        Metadata_IsDirty = false;
        ResetDict();

        //ricarico i valori dei dizionari
        foreach (SteveBrain item in SteveList)
        {
            Skills skills = item.model.Skills;
            AddSum(skills);
            SetMax(skills);
            SetMin(skills);
        }

    }

    private void AddSum(Skills skills)
    {
        foreach (SkillsEnum key in sumSkillDict.Keys.ToArray() )
        {
            sumSkillDict[key] += skills.GetValue(key);
        }
    }

    private void SetMax(Skills skills)
    {
        foreach (SkillsEnum key in maxSkillDict.Keys.ToArray())
        {
            if (skills.GetValue(key) > maxSkillDict[key])
                maxSkillDict[key] = skills.GetValue(key);
        }
    }

    private void SetMin(Skills skills)
    {
        foreach (SkillsEnum key in minSkillDict.Keys.ToArray())
        {
            if (skills.GetValue(key) < minSkillDict[key])
                minSkillDict[key] = skills.GetValue(key);
        }
    }

    internal float GetMaxSkill(SkillsEnum skill)
    {
        if (Metadata_IsDirty) RefreshMetadata();
        return maxSkillDict[skill];
    }

    internal float GetMinSkill(SkillsEnum skill)
    {
        if (Metadata_IsDirty) RefreshMetadata();
        return minSkillDict[skill];
    }

    // Restituisce un numero da 0-1 della skill selezionata
    // Rappresenta la media di quella skill sulla popolazione
    internal float ToPercentage(SkillsEnum skill, Skills skills)
    {
        float max = GetMaxSkill(skill);
        float min = GetMinSkill(skill);

        float delta = max - min;
        float delta_value = max - skills.ValueSkill[skill];

        if (delta == 0) return 0.5f;        //fix tutti i personaggi uguali

        return 1 - delta_value / delta;
    }

    
    // Restituisce la media delle skill del personaggio
    // Es.    ( 0.5 + 0.7 + 1 + ... ) / n
    internal float GetAVGSkills(Skills skills)
    {
        float sum = ToPercentage(SkillsEnum.Intelligence, skills) +
                    ToPercentage(SkillsEnum.Agility, skills) +
                    ToPercentage(SkillsEnum.Appeal, skills) +
                    ToPercentage(SkillsEnum.Charm, skills) +
                    ToPercentage(SkillsEnum.Resistance, skills) +
                    ToPercentage(SkillsEnum.Strenght, skills);
        return sum / Skills.NUMBER_OF_SKILLS;
    }

    // Aggiorna il valore di una data sckill su un dato personaggio
    public void SetSkill(SkillsEnum skill, int value, Skills skills)
    {
        Metadata_IsDirty = true;
        int prec = value;

        if (skills.ValueSkill.ContainsKey(skill) == false)
            skills.ValueSkill.Add(skill, value);
        else
            skills.ValueSkill[skill] = value;

        //TODO: ottimizzare usando la ridondanza per ridurre la complessità
        //FrenzyPeopleController.RecalculateMin(skill);
        //FrenzyPeopleController.RecalculateMax(skill);
    }

    #endregion

    // accoppia 2 individui secondo le variabili genetiche impostate
    public SteveBrain Couple(SteveModel A, SteveModel B)
    {
        Genetic genetic = new Genetic(this);
        SteveModel model = genetic.Couple(A, B, geneticVariable);
        SteveBrain brain = CreateCharacter(model);
        return brain;
    }

    /// <summary>
    /// Chiede il massimo degli exp dato un livello
    /// Modificare la funzione per avere un livello diverso
    /// </summary>
    public int GetMaxExp(int level)
    {
        //((2 ^ lv) + 4000)
        int offset = 4000;
        int basediv = 100;
        double raw = Math.Pow(2, level) + offset;
        int result = (int)raw / basediv;
        return result * basediv;
    }

    /// <summary>
    /// Chiede il massimo degli HP dato un livello
    /// Modificare la funzione per avere un livello diverso
    /// </summary>
    public int GetMaxHP(int level)
    {
        //20 + 20 * 0.1 * (lv-1)
        return (int)(StartHP + StartHP * 0.1 * (level-1));
    }

}
